package filestore

import (
	"app-box/api_error"
	"app-box/config"
	"app-box/tools/app_log"
	"encoding/json"
	"fmt"
	"path/filepath"
	"regexp"
	"strings"
)

// 可能包含文件链接的字符串
type FileString string

// 文件对象的相关信息
type FileKey struct {
	Type      string // 类型
	ObjectKey string // 文件的Key
}

func (key FileKey) ToFileStr() FileString {
	return FileString(fmt.Sprintf("<-file type=\"%s\" key=\"%s\" ->", key.Type, key.ObjectKey))
}

const (
	FileKeyTypeLocal = "local" // 本地文件
	FileKeyTypeQiNiu = "qiniu" // 七牛云文件

	fileEncodeRegex = `<-file`
	fileDecodeRegex = `<--file`
	fileMatchRegex  = `<-file type="(.+?)" key="(.+?)" ->`
)

func getUrlsMatchRegex(urls []string) string {
	for index, url := range urls {
		url = strings.Replace(url, ".", "\\.", -1)
		if strings.HasSuffix(url, "/") {
			url = url[:len(url)-1]
		}
		urls[index] = fmt.Sprintf("(?:%s)", url)
	}
	regexStr := fmt.Sprintf(`\b(%s)(/[a-zA-Z0-9/\-_.~%%]+)`, strings.Join(urls, "|"))
	return regexStr
}

// 将一个包含文件链接的普通字符串转换为FileString，将会格式化能够识别的链接。方便文件迁移
// 例如 http://localfile.com/aa/bb/cc.jpg 会被替换为 \-file type="" key="/aa/bb/cc.jpg"-/
func NewFileString(str string) (fileStr FileString, err api_error.ApiErrorType) {
	reg, e := regexp.Compile(getUrlsMatchRegex([]string{config.LocalFile.AccessURL, config.QiniuKodo.AccessURL}))
	if e != nil {
		err = api_error.RegexErro(e)
		return
	}
	str = strings.ReplaceAll(str, "<-file", "<--file")
	newStr := reg.ReplaceAllStringFunc(str, func(s string) string {
		matchs := reg.FindStringSubmatch(s)
		var fileType = ""
		var objectKey = matchs[2]
		switch matchs[1] {
		case config.LocalFile.AccessURL:
			fileType = FileKeyTypeLocal
			if strings.HasPrefix(objectKey, "/api/file/") {
				objectKey = objectKey[9:]
			}
		case config.QiniuKodo.AccessURL:
			fileType = FileKeyTypeQiNiu
		}
		return string(FileKey{fileType, objectKey}.ToFileStr())
	})

	fileStr = FileString(newStr)
	return
}

func (filestr FileString) ToString() string {
	str := string(filestr)
	reg, e := regexp.Compile(fileMatchRegex)
	if e != nil {
		err := api_error.RegexErro(e)
		app_log.Error(err.Error())
		return str
	}
	str = reg.ReplaceAllStringFunc(str, func(s string) string {
		strs := reg.FindStringSubmatch(s)
		switch strs[1] {
		case FileKeyTypeLocal:
			return fmt.Sprintf("%s%s", config.LocalFile.AccessURL, filepath.Join("/api/file/", strs[2]))
		case FileKeyTypeQiNiu:
			return fmt.Sprintf("%s%s", config.QiniuKodo.AccessURL, strs[2])
		}
		return s
	})
	str = strings.ReplaceAll(str, "<--file", "<-file")
	return str
}

func (filestr FileString) ReplaceKeysForEtch(do func(key FileKey) FileKey) FileString {
	str := string(filestr)
	reg, e := regexp.Compile(fileMatchRegex)
	if e != nil {
		err := api_error.RegexErro(e)
		app_log.Error(err.Error())
	}
	newStr := reg.ReplaceAllStringFunc(str, func(s string) string {
		matchs := reg.FindStringSubmatch(s)
		newKey := do(FileKey{Type: matchs[1], ObjectKey: matchs[2]})

		return string(newKey.ToFileStr())
	})
	return FileString(newStr)
}

func (filestr FileString) GetKeys() []FileKey {
	str := string(filestr)
	reg, e := regexp.Compile(fileMatchRegex)
	if e != nil {
		err := api_error.RegexErro(e)
		app_log.Error(err.Error())
		return []FileKey{}
	}
	matchs := reg.FindAllStringSubmatch(str, -1)
	var list = make([]FileKey, len(matchs))
	for index, item := range matchs {
		list[index] = FileKey{
			Type:      item[1],
			ObjectKey: item[2],
		}
	}
	return list
}

func (filestr FileString) MarshalJSON() (bytes []byte, err error) {
	var str = filestr.ToString()
	return json.Marshal(str)
}

func (filestr *FileString) UnmarshalJSON(b []byte) error {
	var str string
	if err := json.Unmarshal(b, &str); err != nil {
		return err
	}
	newStr, err := NewFileString(str)
	if err != nil {
		return err
	}
	*filestr = newStr
	return nil
}
